﻿/*
 * This is a simple window manager that can be used to simplify
 * the management of WebViews.
 * 
 * The UIManager instance handles UIForms as a linked list.
 * The z-order of a form is determined by its location in the list.
 * The topmost form is the last link in the list.
*/

using AwesomiumDotNet;
using SampleFramework;
using SlimDX;
using SlimDX.Direct3D9;
using SlimDX.DirectInput;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace SlimDXSample
{
    public class UIManager : IResource
    {
        SlimDX.Direct3D9.Device device;
        WebCore webCore;
        GameWindow window;

        public SlimDX.Direct3D9.Device Device
        {
            get { return device; }
        }
        public WebCore WebCore
        {
            get { return webCore; }
        }
        public GameWindow Window
        {
            get { return window; }
        }
        public UIForm topForm, bottomForm;
        public bool coreUpdated = false;
        public Sprite sprite;

        public UIManager(SlimDX.Direct3D9.Device device, GameWindow window)
        {
            this.device = device;
            this.window = window;

            // Initialize WebCore
            WebCoreOptions options = new WebCoreOptions();
            options.EnablePlugins = true;
            options.LogLevel = LogLevel.None;
            webCore = new WebCore(options);
            webCore.BaseDirectory = "html\\";
            
            topForm = bottomForm = null;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                UIForm form = topForm;
                while (form != null)
                {
                    form.Dispose();
                    form = form.Previous;
                }
                webCore.Dispose();

                if (sprite != null)
                {
                    sprite.Dispose();
                    sprite = null;
                }
            }
        }

        public void Add(UIForm form)
        {
            // Put the new form on top
            if (topForm == null)
            {
                // No forms exist, so this form is top and bottom
                form.Previous = null;
                bottomForm = form;
            }
            else
            {
                form.Previous = topForm;
                form.Previous.Next = form;
            }
            form.Next = null;
            topForm = form;
        }

        public void AddToBottom(UIForm form)
        {
            if (bottomForm != null)
            {
                bottomForm.Previous = form;
                form.Next = bottomForm;
            }
            bottomForm = form;
        }

        public void Remove(UIForm form)
        {
            if (form == topForm)
            {
                topForm = form.Previous;
                if (topForm == null)
                    bottomForm = null;
                else
                    topForm.Next = null;
            }

            else if (form == bottomForm)
            {
                bottomForm = form.Next;
                bottomForm.Previous = null;
            }

            else
            {
                form.Previous.Next = form.Next;
                form.Next.Previous = form.Previous;
            }
            form.Previous = null;
            form.Next = null;
        }

        public void MouseEvent(Input input)
        {
            Rectangle wndRect = new Rectangle();

            UIForm form = topForm;

            // Find the form that was clicked on
            while (form != null)
            {
                wndRect.X = (int)(form.x - (int)form.cx);
                wndRect.Y = (int)(form.y - (int)form.cy);
                wndRect.Width = form.Width;
                wndRect.Height = form.Height;
                if (wndRect.Contains(input.MousePoint))
                {
                    form.MouseMove(input.MousePoint.X - wndRect.X, input.MousePoint.Y - wndRect.Y);
                    if (input.MouseDown != MouseButtons.None)
                    {
                        if ((input.MouseDown & MouseButtons.Left) == MouseButtons.Left)
                            form.MouseDown(MouseButton.Left);

                        if ((input.MouseDown & MouseButtons.Right) == MouseButtons.Right)
                            form.MouseDown(MouseButton.Right);
                    }
                    if (input.MouseUp != MouseButtons.None)
                    {
                        if ((input.MouseUp & MouseButtons.Left) == MouseButtons.Left)
                            form.MouseUp(MouseButton.Left);

                        if ((input.MouseUp & MouseButtons.Right) == MouseButtons.Right)
                            form.MouseUp(MouseButton.Right);
                    }

                    break;
                }
                form = form.Previous;
            }
        }

        // Returns true if all keys were handled
        public bool KeyboardEvent(Input input)
        {
            if (input.keyboardDown.Count == 0 && input.keyboardUp.Count == 0)
                return false;

            // Find a form that accepts input
            UIForm form = topForm;
            while (form != null)
            {
                if (form.takesKeystrokes == true)
                {
                    form.KeyBoardEvent(input);
                    return true;
                }
                form = form.Previous;
            }
            return false;
        }

        public void Initialize(GraphicsDeviceManager graphicsDeviceManager)
        {
            sprite = new Sprite(device);
        }

        public void LoadContent()
        {
            sprite.OnResetDevice();
            AdjustScaling();
        }

        public void UnloadContent()
        {
            sprite.OnLostDevice();

            UIForm form = topForm;
            while (form != null)
            {
                form.OnLostDevice();
                form = form.Previous;
            }
        }

        public void AdjustScaling()
        {
            UIForm form = topForm;
            while (form != null)
            {
                form.AdjustScaling();
                form = form.Previous;
            }
        }

        public void Draw()
        {
            UIForm form = bottomForm;
            while (form != null)
            {
                form.Draw();
                form = form.Next;
            }
            coreUpdated = false;
        }
    };

    [FlagsAttribute]
    public enum UIFlags
    {
        None = 0,
        CenterVertical = 1,
        CenterHorizontal = 2,
        Center = CenterVertical | CenterHorizontal,
        FitWidth = 4,
        FitHeight = 8,
        AlignLeft = 0,
        AlignRight = 16,
        AlignTop = 0,
        AlignBottom = 32,
        AlignBottomRight = AlignBottom | AlignRight,
    };

    public class UIForm : IDisposable
    {
        UIManager ui;
        WebView webView;
        UIForm prev, next;

        public UIManager UIManager
        {
            get { return ui; }
        }
        public WebView WebView
        {
            get{ return webView; }
        }
        public UIForm Previous
        {
            get { return prev; }
            set { prev = value; }
        }
        public UIForm Next
        {
            get { return next; }
            set { next = value; }
        }
        public int Width
        {
            get { return width; }
            set { width = value; }
        }
        public int Height
        {
            get { return height; }
            set { height = value; }
        }
        public bool IsLoading
        {
            get { return isLoading; }
            set { isLoading = value; }
        }
        public bool IsTransparent
        {
            get { return isTransparent; }
            set
            {
                if (isTransparent != value)
                {
                    webView.SetTransparent(value);
                    tex.Dispose();
                    tex = null;
                    isTransparent = value;
                    AdjustScaling();
                }
            }
        }
        public UIFlags UIFlags
        {
            get { return flags; }
            set { flags = value; }
        }

        public float x = 0, y = 0;
        public float cx = 0, cy = 0;
        int width, height;
        UIFlags flags;

        public bool pageLoaded = false;
        public bool takesKeystrokes = true;
        bool isLoading = false;
        bool isTransparent = true;

        private Texture tex = null;

        public UIForm(UIManager ui, int width, int height, UIFlags flags)
        {
            this.ui = ui;
            this.width = width;
            this.height = height;
            this.flags = flags;

            // Initialize Awesomium
            webView = ui.WebCore.CreateWebView(width, height, isTransparent);
            webView.FinishLoading += new EventHandler<FinishLoadingEventArgs>(OnFinishLoading);
            webView.ChangeCursor += new EventHandler<ChangeCursorEventArgs>(OnChangeCursor);
            webView.SetTransparent(isTransparent);

            ui.Add(this);
        }

        public UIForm(UIManager ui, int width, int height)
            : this(ui, width, height, UIFlags.None)
        {
        }

        public void Initialize()
        {
            AdjustScaling();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                if (isLoading)
                {
                    ExecuteJavaScript("window.stop();");
                    System.Threading.Thread.Sleep(100);
                }

                webView.Destroy();
                if (tex != null)
                {
                    tex.Dispose();
                    tex = null;
                }
            }
        }

        public void SetBottom()
        {
            ui.Remove(this);
            ui.AddToBottom(this);
        }

        void OnFinishLoading(Object sender, FinishLoadingEventArgs e)
        {
            pageLoaded = true;
            isLoading = false;
        }

        void OnChangeCursor(Object sender, ChangeCursorEventArgs e)
        {
            ui.Window.Cursor = e.Cursor;
        }

        public void CreateObject(String objectName)
        {
            webView.CreateObject(objectName);
        }

        public void SetCallbackName(String objectName, String callbackName)
        {
            webView.SetObjectCallback(objectName, callbackName);
        }

        public void SetCallback(EventHandler<CallbackEventArgs> callback)
        {
            webView.Callback += callback;
        }

        public void MouseMove(int x, int y)
        {
            webView.InjectMouseMove(x, y);
        }

        public void MouseDown(MouseButton key)
        {
            webView.InjectMouseDown(key);
        }

        public void MouseUp(MouseButton key)
        {
            webView.InjectMouseUp(key);
        }

        public void KeyBoardEvent(Input input)
        {
            foreach (Key key in input.keyboardDown)
            {
                WebKeyboardEvent keyEvent = new WebKeyboardEvent();

                bool shiftPressed = false;
                bool controlPressed = false;

                if (input.KeyboardState.IsPressed(Key.LeftShift) |
                    input.KeyboardState.IsPressed(Key.RightShift))
                {
                    shiftPressed = true;
                    keyEvent.Modifiers |= Modifiers.ShiftKey;
                }

                if (input.KeyboardState.IsPressed(Key.LeftControl) |
                    input.KeyboardState.IsPressed(Key.RightControl))
                {
                    controlPressed = true;
                    keyEvent.Modifiers |= Modifiers.ControlKey;
                }

                bool ischar = true;
                keyEvent.Text = Input.KeyToChar(key, shiftPressed, out ischar);
                if (keyEvent.Text != '\0')
                {
                    keyEvent.VirtualKeyCode = keyEvent.Text;
                    if (ischar)
                    {
                        if (controlPressed)
                        {
                            switch ((char)keyEvent.Text)
                            {
                                case 'a':
                                    webView.SelectAll();
                                    continue;
                                case 'x':
                                    webView.Cut();
                                    continue;
                                case 'c':
                                    webView.Copy();
                                    continue;
                                case 'v':
                                    webView.Paste();
                                    continue;
                            }
                        }

                        keyEvent.EventType = KeyboardEventType.KeyDownType;
                        WebView.InjectKeyboardEvent(keyEvent);
                        keyEvent.EventType = KeyboardEventType.CharType;
                        WebView.InjectKeyboardEvent(keyEvent);
                    }
                    else
                    {
                        keyEvent.EventType = KeyboardEventType.KeyDownType;
                        WebView.InjectKeyboardEvent(keyEvent);
                    }
                }
            }
        }

        public void LoadUrl(string url)
        {
            isLoading = true;
            webView.LoadUrl(url);
        }

        public void LoadHtml(string html)
        {
            isLoading = true;
            webView.LoadHtml(html);
        }

        public void LoadFile(string fileName)
        {
            isLoading = true;
            webView.LoadFile(fileName);
        }

        public void ExecuteJavaScript(string javaScript)
        {
            webView.ExecuteJavaScript(javaScript);
        }

        public void Draw()
        {
            if (UIManager.coreUpdated == false)
            {
                UIManager.WebCore.Update();
                UIManager.coreUpdated = true;
            }
            if (pageLoaded == false)
                return;

            if (webView.IsDirty())
            {
                Surface surface = tex.GetSurfaceLevel(0);
                DataRectangle rect = surface.LockRectangle(LockFlags.Discard);
                IntPtr surface_ptr = rect.Data.DataPointer;
                webView.Render(surface_ptr, rect.Pitch, 4);
                surface.UnlockRectangle();
            }

            Vector3 center, position;
            center = position = Vector3.Zero;

            if ((flags & UIFlags.CenterHorizontal) == UIFlags.CenterHorizontal)
            {
                center.X = cx;
                position.X = x;
            }
            if ((flags & UIFlags.CenterVertical) == UIFlags.CenterVertical)
            {
                center.Y = cy;
                position.Y = y;
            }

            if ((flags & UIFlags.AlignRight) == UIFlags.AlignRight)
            {
                center.X = cx;
                position.X = x;
            }
            if ((flags & UIFlags.AlignBottom) == UIFlags.AlignBottom)
            {
                center.Y = cy;
                position.Y = y;
            }

            if (isTransparent)
                ui.sprite.Begin(SpriteFlags.AlphaBlend);
            else
                ui.sprite.Begin(SpriteFlags.None);
            ui.sprite.Draw(tex, center, position, Color.White);
            ui.sprite.End();
        }

        public void AdjustScaling()
        {
            bool sizeModified = false;

            if ((flags & UIFlags.FitWidth) == UIFlags.FitWidth)
            {
                width = UIManager.Device.Viewport.Width;
                sizeModified = true;
            }

            if ((flags & UIFlags.FitHeight) == UIFlags.FitHeight)
            {
                height = UIManager.Device.Viewport.Height;
                sizeModified = true;
            }

            if ((flags & UIFlags.CenterHorizontal) == UIFlags.CenterHorizontal)
                x = UIManager.Device.Viewport.Width / 2;
            if ((flags & UIFlags.CenterVertical) == UIFlags.CenterVertical)
                y = UIManager.Device.Viewport.Height / 2;

            if ((flags & UIFlags.CenterHorizontal) == UIFlags.CenterHorizontal)
                cx = width / 2;
            if ((flags & UIFlags.CenterVertical) == UIFlags.CenterVertical)
                cy = height / 2;

            if ((flags & UIFlags.AlignRight) == UIFlags.AlignRight)
            {
                x = UIManager.Device.Viewport.Width / 2;
                cx = width - x;
            }
            if ((flags & UIFlags.AlignBottom) == UIFlags.AlignBottom)
            {
                y = UIManager.Device.Viewport.Height / 2;
                cy = height - y;
            }

            if (sizeModified || tex == null)
            {
                if (tex != null)
                {
                    tex.Dispose();
                }
                tex = new Texture(UIManager.Device, width, height, 1, Usage.Dynamic,
                    isTransparent ? Format.A8R8G8B8 : Format.X8R8G8B8, Pool.Default);
                webView.Resize(width, height);
            }
        }

        public void OnLostDevice()
        {
            if (tex != null)
            {
                tex.Dispose();
                tex = null;
            }
        }
    };


    public class UIInfoForm : UIForm
    {
        public UIInfoForm(UIManager ui, int width, int height, UIFlags flags)
            : base(ui, width, height, flags)
        {
        }

        public UIInfoForm(UIManager ui)
            : this(ui, 256, 300, UIFlags.None)
        {
            LoadFile("info.html");
            takesKeystrokes = false;
        }

        public void SetText(string text)
        {
            ExecuteJavaScript("setText(\"" + text + "\");");
        }
}
}
